#include <functional>
#include "engine_impl.hh"
#include "path_pool.hh"
#include "../corelib/grid/path.hh"
#include "../corelib/grid/site.hh"

namespace kratos { namespace engine {

bool EngineImpl::registerCallbacks() {
    hashing_.start([&](std::uint64_t oldPath, std::uint64_t newPath) {
        using EntityInfoVector = std::vector<EntityInfo>;
        EntityInfoVector entities;
        for (auto it : locatorEntityMap_) {
            if (it.second.pathID == oldPath) {
                entities.push_back(it.second);
            }
        }
        for (auto info : entities) {
            auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
            for (auto modulePtr : modules) {
                auto service = modulePtr->getService();
                if (service) {
                    try {
                        service->onLocatorChange(info.entityID, info.entityType, oldPath, newPath);
                    } catch (std::exception& e) {
                        ENGINE_LOG_EXCEPTION(e, "Service::onLocatorChange, entity ID:", info.entityID);
                    }
                }
            }
        }
    });
    grid_->onJoinCluster([&](const grid::SiteID& id, bool isRouter, bool isLeader) {
        if (isRouter) {      
            logwcom("Engine") << "Locator joining cluster, site ID [" << id.toString() << "]" << corelib::logend;
        }
    });
    grid_->onLeaveCluster([&](const grid::SiteID& id, bool isRouter, bool isLeader) {
        if (isRouter) {
            logwcom("Engine") << "Locator leaving cluster, site ID [" << id.toString() << "]" << corelib::logend;
        }
    });
    grid_->onJoin([&](grid::Site& site) {
        if (site.isRouter() && !grid_->property().isLeader) {
            locatorSites_.push_back(site.getID());
            // find locator
            logwcom("Engine") << "Start find path to locator [" << site.getID().toString() << "]" << corelib::logend;
            //pathPool_->getPath(site.getID(), site.getID());
            internalFindPath(site.getID(), site.getID());
        }
        // call service's callbackss
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto modulePtr : modules) {
            auto service = modulePtr->getService();
            if (service) {
                try {
                    service->onSiteJoin(site.getID());
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onSiteJoin, site ID:", site.getID().toString());
                }
            }
        }
    });
    grid_->onClose([&](grid::Site& site) {
        // call service's callback
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto modulePtr : modules) {
            auto service = modulePtr->getService();
            if (service) {
                try {
                    service->onSiteDisjoin(site.getID());
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onSiteDisjoin, site ID:", site.getID().toString());
                }
            }
        }
    });
    grid_->onData([&](grid::Site& site, const char* data, std::size_t size) {
        // call service's callback
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto modulePtr : modules) {
            auto service = modulePtr->getService();
            if (service) {
                try {
                    service->onSiteData(site.getID(), data, size);
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onSiteData, site ID:", site.getID().toString());
                }
            }
        }
    });
    grid_->pathManager().onJoin(
        [&](grid::Path& path, grid::Grid& grid) {
        //auto siteType = path.serviceGridType();
        //pathPool_->addPath(siteType, path.getID());
        auto& peerSiteID = path.getPeerSiteID();
        auto isLocator = (std::find(locatorSites_.begin(), locatorSites_.end(), peerSiteID) != locatorSites_.end());
        if (isLocator) {
            // Pushes locator's path
            locatorPathes_.push_back(path.getID());
            // engine internal path for locator
            if (!hashing_.addNode(peerSiteID.u64(), path.getID())) {
                logwcom("Engine") << "Add hashing node for locator failed, site ID:" << peerSiteID.toString() << corelib::logend;
            } else {
                // call service's callback
                auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
                for (auto modulePtr : modules) {
                    auto service = modulePtr->getService();
                    if (service) {
                        try {
                            service->onLocatorJoin(path.getID());
                        } catch (std::exception& e) {
                            ENGINE_LOG_EXCEPTION(e, "Service::onLocatorJoin, pathID:", path.getID());
                        }
                    }
                }
                logwcom("Engine") << "Locator path joined, site ID [" << peerSiteID.toString() << "]" << corelib::logend;
            }
            return;
        }
        //logwcom("Engine") << "Path join [" << peerSiteID.toString() << "] <-> [" << grid.property().id.toString() << "]"
        //    << logend;
        // call service's callback
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto modulePtr : modules) {
            auto service = modulePtr->getService();
            if (service) {
                try {
                    service->onPathJoin(path.getID(), path.serviceGridType(), path.getUserData());
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onPathJoin, pathID:", path.getID());
                }
            }
        }
        //logvcom("engine") << "Path established for site type:" << path.serviceGridType() << logend;
    });
    grid_->pathManager().onClose(
        [&](grid::Path& path, grid::Grid& grid) {
        //auto userData = static_cast<SiteType>(path.getUserData());
        //auto siteType = path.serviceGridType();
        //pathPool_->removePath(siteType, path.getID());
        auto& peerSiteID = path.getPeerSiteID();
        auto isLocator = (std::find(locatorSites_.begin(), locatorSites_.end(), peerSiteID) != locatorSites_.end());
        if (isLocator) {
            // Remove the locator's path
            locatorPathes_.erase(std::remove(locatorPathes_.begin(), locatorPathes_.end(), path.getID()), locatorPathes_.end());
            // remove internal locator path
            hashing_.removeNode(path.getPeerSiteID().u64());
            logwcom("Engine") << "Locator path dis-joined, site ID [" << peerSiteID.toString() << "]" << corelib::logend;
        }
        // call service's callback
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto modulePtr : modules) {
            auto service = modulePtr->getService();
            if (service) {
                try {
                    if (isLocator) {
                        service->onLocatorDisjoin(path.getID());
                    }
                    if (grid_->property().isRouter) {
                        // The path that entitis lives in was broken
                        removeEntityPath(path.getID());
                    }
                    service->onPathDisjoin(path.getID(), path.serviceGridType());
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onPathDisjoin, pathID:", path.getID());
                }
            }
        }
    });
    grid_->pathManager().onData(
        [&](grid::Path& path, grid::Grid& grid, const char* data, std::size_t size) {
        // call service's callback
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto modulePtr : modules) {
            auto service = modulePtr->getService();
            if (service) {
                try {
                    service->onPathData(path.getID(), path.serviceGridType(), data, size);
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onPathData, pathID:", path.getID());
                }
            }
        }
    });
    grid_->pathManager().onFindTimeout([&](std::uint64_t, grid::Grid& grid, std::uint16_t siteType, std::uint64_t userData) {
        // refind
        auto isLocator = (std::find(locatorSites_.begin(), locatorSites_.end(), userData) != locatorSites_.end());
        if (isLocator) {
            grid_->pathManager().find(grid::SiteID(userData), userData, findTimeout_, findTimeout_);
        }  else {
            auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
            for (auto modulePtr : modules) {
                auto service = modulePtr->getService();
                if (service) {
                    try {
                        service->onFindPathTimeout(siteType, userData);
                    } catch (std::exception& e) {
                        ENGINE_LOG_EXCEPTION(e, "Service::onFindPathTimeout, siteType:", siteType);
                    }
                }
            }
            grid_->pathManager().find(siteType, userData, findTimeout_, findTimeout_);
        }
    });
    // sessions
    grid_->onSessionAccept([&](network::Session& session, grid::Grid& grid) {
        if (session.getIoType() == kratos::network::IoLoopType::HTTP) {
            // Setup parser
            session.setUserPtr(corelib::allocate<util::HttpProto>());
        }
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto modulePtr : modules) {
            auto service = modulePtr->getService();
            if (service) {
                try {
                    service->onSessionJoin(session.getID());
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onSessionJoin, session ID:", session.getID());
                }
            }
        }
    });
    grid_->onSessionClose([&](network::Session& session, grid::Grid& grid) {
        if (session.getIoType() == kratos::network::IoLoopType::HTTP) {
            auto parser = reinterpret_cast<util::HttpProto*>(session.getUserPtr());
            if (parser) {
                corelib::dispose(parser);
            }
            session.setUserPtr(nullptr);
        }
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto modulePtr : modules) {
            auto service = modulePtr->getService();
            if (service) {
                try {
                    service->onSessionDisjoin(session.getID());
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onSessionDisjoin, session ID:", session.getID());
                }
            }
        }
    });
    //grid_->onSessionConnect([&](network::Session& session, grid::Grid& grid) {
    //    if (session.getIoType() == kratos::network::IoLoopType::HTTP) {
    //        session.setUserPtr(reinterpret_cast<void*>(kratos::corelib::allocate<util::HttpProto>()));
    //    }
    //    auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
    //    for (auto modulePtr : modules) {
    //        auto service = modulePtr->getService();
    //        if (service) {
    //            try {
    //                service->onSessionConnected(session.getID(), session.getUserData());
    //            } catch (std::exception& e) {
    //                ENGINE_LOG_EXCEPTION(e, "Service::onSessionDisjoin, session ID:", session.getID());
    //            }
    //        }
    //    }
    //});
    grid_->onSessionData([&](network::Session& session, grid::Grid& grid) {
        std::size_t bytes = 0;
        char buffer[NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE] = { 0 };
        auto& reader = session.getReader();
        if (session.getIoType() == kratos::network::IoLoopType::HTTP) {
            auto parser = reinterpret_cast<util::HttpProto*>(session.getUserPtr());
            if (!parser) {
                return;
            }
            kratos::util::HttpType httpType = session.getSelectorType() == kratos::network::SelectorType::Connector ?
                kratos::util::HttpType::RESPONSE : kratos::util::HttpType::REQUEST;
            if (!parser->parse(reader, bytes, httpType)) {
                // TODO deal with chunked
                return;
            }
            auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
            for (auto modulePtr : modules) {
                auto service = modulePtr->getService();
                try {
                    service->onHttpData(session.getID(), *parser);
                } catch (std::exception& e) {
                    ENGINE_LOG_EXCEPTION(e, "Service::onSessionData, session ID:", session.getID());
                }
                // Dispose parser
                session.setUserPtr(nullptr);
                corelib::dispose(parser);
            }
        } else {
            std::size_t size = 0;
            if (protocolAnalyzer_.isParsed()) {
                while (protocolAnalyzer_.hasFullPacket(reader, size)) {
                    bytes = reader.available() > NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE ?
                        NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE : size;
                    bytes = reader.pop(buffer, bytes);
                    auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
                    for (auto modulePtr : modules) {
                        auto service = modulePtr->getService();
                        try {
                            service->onSessionData(session.getID(), buffer, bytes);
                        } catch (std::exception& e) {
                            ENGINE_LOG_EXCEPTION(e, "Service::onSessionData, session ID:", session.getID());
                        }
                    }
                }
            } else {
                size = reader.available();
                while (size) {
                    bytes = size > NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE ?
                        NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE : size;
                    bytes = reader.pop(buffer, bytes);
                    auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
                    for (auto modulePtr : modules) {
                        auto service = modulePtr->getService();
                        try {
                            service->onSessionData(session.getID(), buffer, bytes);
                        } catch (std::exception& e) {
                            ENGINE_LOG_EXCEPTION(e, "Service::onSessionData, session ID:", session.getID());
                        }
                    }
                    size = reader.available();
                }
            }
        }
    });
    grid_->onSessionRecvTimeout([&](network::Session& session, grid::Grid& grid) {
        session.close();
    });
    return true;
}

}}
